package society.framework.core.xml;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import society.framework.core.communication.Message;
import society.framework.core.communication.MessageRouter;

/**
 * Fully deserializable and serializable XML Automaton.
 * It extends the DeserializableOnlyXMLAutomaton and uses a PrintStream for serialization.
 * @author Micu Andrei
 *
 */
public class XMLAutomaton extends DeserializableOnlyXMLAutomaton {

	
	/* **************************************** SERIALIZATION  **************************************** */
	
	@Override
	public String serializeToString() {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		serializeToStream(baos);
		return baos.toString();
	}

	@Override
	public boolean serializeToStream(OutputStream fos) {
		try{
			XMLAutomatonSerializer xas = new XMLAutomatonSerializer();
			xas.serialize(fos);
			return true;
			
		} catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Class used to serialize the current Automaton.
	 * @author Micu Andrei
	 *
	 */
	protected class XMLAutomatonSerializer{
		protected PrintStream outputStream;

		public XMLAutomatonSerializer() {
		}
		
		/**
		 * Begins the serialization of the current Automaton to the given OutputStream.
		 * @param os - stream where the automaton serialization is written.
		 * @return - true if operation succeeded, false otherwise
		 */
		public boolean serialize(OutputStream os){
			outputStream = new PrintStream(os);
			outputStream.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");						//header of the XML
			outputStream.printf("<%s %s=\"%s\" %s=\"%s\" %s=\"%s\">", AUTOMATON_TAG,				//automaton declaration
					NAME_ATTRIBUTE, XMLAutomaton.this.name,
					CURRENT_STATE_ATTRIBUTE, XMLAutomaton.this.currentStateName,
					REGISTER_AT_START_ATTRIBUTE, 
					MessageRouter.getInstance().getSubscriberNames().contains(XMLAutomaton.this.getName()) ? ".T." : ".F.");
			serializeCurrentMessage();
			serializeMessageList();
			serializeVariables();
			serializeStates();
			outputStream.printf("</%s>", AUTOMATON_TAG);
			outputStream.flush();
			return true;
		}
		
		protected void serializeCurrentMessage(){
			if(currentMessage!=null){
				outputStream.printf("<%s %s=\"%s\" %s=\"%s\" />", CURRENT_MESSAGE_TAG,
						FROM_ATTRIBUTE, currentMessage.from != null ? currentMessage.from : "",
						DATA_ATTRIBUTE, currentMessage.data != null ? currentMessage.data : "");
			}
		}
		
		protected void serializeMessageList(){
			outputStream.printf("<%s>", MESSAGE_LIST_TAG);
			Message message;
			while((message=XMLAutomaton.this.messageList.remove(1))!=null){
				outputStream.printf("<%s %s=\"%s\" %s=\"%s\" />", MESSAGE_TAG,
						FROM_ATTRIBUTE, message.from,
						DATA_ATTRIBUTE, message.data);
			}
			outputStream.printf("</%s>", MESSAGE_LIST_TAG);
		}

		protected void serializeVariables() {
			outputStream.printf("<%s>", VARIABLES_TAG);
			for(Map.Entry<String, Object> entry : variables.entrySet()){
				serializeVariable(entry.getKey(), entry.getValue());
			}
			outputStream.printf("</%s>", VARIABLES_TAG);
		}

		protected void serializeVariable(String name, Object value) {
			if(value instanceof HashMap<?, ?>){					//when it's a Dictionary write the variables in it recursively
				for(Entry<?, ?> entry : ((HashMap<?, ?>) value).entrySet()){
					String keyString = name;
					Object keyObject = entry.getKey();
					
					if(keyObject instanceof String){		//key string construction
						keyString += "['" + keyObject.toString() + "']";
					} else if(keyObject instanceof Boolean){
						if((Boolean)keyObject){
							keyString += "[.T.]";
						} else {
							keyString += "[.F.]";
						}
					} else {
						keyString += "[" + keyObject.toString() + "]";
					}

					serializeVariable(keyString, entry.getValue());
				}
				
			} else {												//when it's not a Dictionary write it normally
				String valueString = null;
				
				if(value instanceof String){
					valueString = "'" + value.toString() + "'";
				} else if(value instanceof Boolean){
					if((Boolean)value){
						valueString = ".T.";
					} else {
						valueString = ".F.";
					}
				} else {
					valueString = value.toString();
				}
				
				outputStream.printf("<%s %s=\"%s\" %s=\"%s\" />", 
						VARIABLE_TAG, NAME_ATTRIBUTE, name, VALUE_ATTRIBUTE, valueString);
			}
		}

		protected void serializeStates() {
			outputStream.printf("<%s>", STATES_TAG);
			for(Map.Entry<String, State> entry : states.entrySet()){
				serializeState(entry.getKey(), (XMLState)entry.getValue());
			}
			outputStream.printf("</%s>", STATES_TAG);
		}

		protected void serializeState(String name, XMLState state) {
			outputStream.printf("<%s %s=\"%s\">", STATE_TAG, NAME_ATTRIBUTE, name);

			outputStream.printf("<%s>", CODE_TAG);					//serialize the code inside the state
			if(state.instruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)state.instruction);
			} else {
				serializeInstruction(state.instruction);
			}
			outputStream.printf("</%s>", CODE_TAG);
			
			if(state.transition instanceof XMLTransitionGroup){		//serialize the transition / transition group
				serializeTransitionGroup(null, (XMLTransitionGroup)state.transition);
			} else {
				serializeTransition(null, (XMLTransition)state.transition);
			}
			
			outputStream.printf("</%s>", STATE_TAG);
		}

		protected void serializeTransitionGroup(String name, XMLTransitionGroup transitionGroup) {
			if(name!=null){
				outputStream.printf("<%s %s=\"%s\">", TRANSITION_GROUP_TAG, NAME_ATTRIBUTE, name);
			} else {
				outputStream.printf("<%s>", TRANSITION_GROUP_TAG);
			}
			
			outputStream.printf("<%s>", CODE_TAG);					//serialize the code inside the state
			if(transitionGroup.instruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)transitionGroup.instruction);
			} else {
				serializeInstruction(transitionGroup.instruction);
			}
			outputStream.printf("</%s>", CODE_TAG);

			for(Map.Entry<String, Transition> entry : transitionGroup.transitions.entrySet()){
				if(entry.getValue() instanceof XMLTransitionGroup){
					serializeTransitionGroup(entry.getKey(), (XMLTransitionGroup)entry.getValue());
				} else {
					serializeTransition(entry.getKey(), (XMLTransition)entry.getValue());
				}
			}

			outputStream.printf("</%s>", TRANSITION_GROUP_TAG);
		}

		protected void serializeTransition(String name, XMLTransition transition) {
			if(name!=null){
				outputStream.printf("<%s %s=\"%s\">", TRANSITION_TAG, NAME_ATTRIBUTE, name);
			} else {
				outputStream.printf("<%s>", TRANSITION_TAG);
			}
			
			if(transition.instruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)transition.instruction);
			} else {
				serializeInstruction(transition.instruction);
			}
			
			outputStream.printf("</%s>", TRANSITION_TAG);
		}

		protected void serializeInstructionBlock(InstructionBlock instructionBlock) {
			for(Instruction instruction : instructionBlock.instructions){
				serializeInstruction(instruction);
			}
		}

		protected void serializeInstruction(Instruction instruction) {
			if(instruction instanceof WhileInstruction){
				serializeWhile((WhileInstruction)instruction);
			} else if(instruction instanceof DoWhileInstruction){
				serializeDoWhile((DoWhileInstruction)instruction);
			} else if(instruction instanceof ForInstruction){
				serializeFor((ForInstruction)instruction);
			} else if(instruction instanceof IfElseInstruction){
				serializeIfElse((IfElseInstruction)instruction);
			} else if(instruction instanceof SwitchInstruction){
				serializeSwitch((SwitchInstruction)instruction);
			} else if(instruction instanceof ExpressionContainerInstruction){
				serializeExpressionContainer((ExpressionContainerInstruction)instruction);
			} else if(instruction instanceof GetNextMessageInstruction){
				serializeGetNextMessage((GetNextMessageInstruction)instruction);
			} else if(instruction instanceof SendMessageInstruction){
				serializeSendMessage((SendMessageInstruction)instruction);
			} else {	//else it's a returning instruction
				serializeReturn(instruction);
			}
		}

		protected void serializeWhile(WhileInstruction instruction) {
			String testExpression = deconstructExpression(instruction.testInstruction);
			outputStream.printf("<%s %s=\"%s\">", WHILE_TAG, TEST_ATTRIBUTE, testExpression);
			
			if(instruction.repeatInstruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
			} else {
				serializeInstruction(instruction.repeatInstruction);
			}

			outputStream.printf("</%s>", WHILE_TAG);
		}

		protected void serializeDoWhile(DoWhileInstruction instruction) {
			String testExpression = deconstructExpression(instruction.testInstruction);
			outputStream.printf("<%s %s=\"%s\">", DO_WHILE_TAG, TEST_ATTRIBUTE, testExpression);
			
			if(instruction.repeatInstruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
			} else {
				serializeInstruction(instruction.repeatInstruction);
			}

			outputStream.printf("</%s>", DO_WHILE_TAG);
		}

		protected void serializeFor(ForInstruction instruction) {
			String assignExpression = deconstructExpression(instruction.assignInstruction);
			String testExpression = deconstructExpression(instruction.testInstruction);
			String stepExpression = deconstructExpression(instruction.stepInstruction);
			outputStream.printf("<%s %s=\"%s\" %s=\"%s\" %s=\"%s\">", FOR_TAG,
					ASSIGN_ATTRIBUTE, assignExpression, TEST_ATTRIBUTE, testExpression, STEP_ATTRIBUTE, stepExpression);
			
			if(instruction.repeatInstruction instanceof InstructionBlock){
				serializeInstructionBlock((InstructionBlock)instruction.repeatInstruction);
			} else {
				serializeInstruction(instruction.repeatInstruction);
			}

			outputStream.printf("</%s>", FOR_TAG);
		}

		protected void serializeIfElse(IfElseInstruction instruction) {
			String testExpression = deconstructExpression(instruction.testInstruction);
			outputStream.printf("<%s %s=\"%s\">", IF_TAG, TEST_ATTRIBUTE, testExpression);
			
			if(instruction.succeedInstruction instanceof InstructionBlock){			//succeed instruction
				serializeInstructionBlock((InstructionBlock)instruction.succeedInstruction);
			} else {
				serializeInstruction(instruction.succeedInstruction);
			}

			outputStream.printf("</%s>", IF_TAG);
			
			if(instruction.failInstruction != null){
				outputStream.printf("<%s>", ELSE_TAG);
				
				if(instruction.failInstruction instanceof InstructionBlock){			//fail instruction
					serializeInstructionBlock((InstructionBlock)instruction.failInstruction);
				} else {
					serializeInstruction(instruction.failInstruction);
				}

				outputStream.printf("</%s>", ELSE_TAG);
			}
		}

		protected void serializeSwitch(SwitchInstruction instruction) {
			String valueExpression = deconstructExpression(instruction.argumentInstruction);

			outputStream.printf("<%s %s=\"%s\">", SWITCH_TAG, VALUE_ATTRIBUTE, valueExpression);
			
			for(CaseInstruction caseInstruction : instruction.caseInstructionsList){	//cases
				valueExpression = deconstructExpression(caseInstruction.label); 
				outputStream.printf("<%s %s=\"%s\">", CASE_TAG, VALUE_ATTRIBUTE, valueExpression);
				
				if(caseInstruction.instruction instanceof InstructionBlock){
					serializeInstructionBlock((InstructionBlock)caseInstruction.instruction);
				} else {
					serializeInstruction(caseInstruction.instruction);
				}

				outputStream.printf("</%s>", CASE_TAG);
			}
			
			if(instruction.defaultInstruction != null){		//default
				outputStream.printf("<%s>", DEFAULT_TAG);
				
				Instruction defaultInstruction = ((CaseInstruction)instruction.defaultInstruction).instruction;
				if(defaultInstruction instanceof InstructionBlock){
					serializeInstructionBlock((InstructionBlock) defaultInstruction);
				} else {
					serializeInstruction(defaultInstruction);
				}
				
				outputStream.printf("</%s>", DEFAULT_TAG);
			}

			outputStream.printf("</%s>", SWITCH_TAG);
		}

		protected void serializeExpressionContainer(ExpressionContainerInstruction instruction) {
			String expressionExpression = deconstructExpression(instruction.expression);
			outputStream.printf("<%s %s=\"%s\" />", EXECUTE_TAG, EXPRESSION_ATTRIBUTE, expressionExpression);
		}

		protected void serializeGetNextMessage(GetNextMessageInstruction instruction) {
			outputStream.printf("<%s />", GET_NEXT_MESSAGE_TAG);
		}

		protected void serializeSendMessage(SendMessageInstruction instruction) {
			String toExpression = deconstructExpression(instruction.to);
			String dataExpression = deconstructExpression(instruction.data);
			outputStream.printf("<%s %s=\"%s\" %s=\"%s\" />", SEND_MESSAGE_TAG,
					TO_ATTRIBUTE, toExpression, DATA_ATTRIBUTE, dataExpression);
		}

		protected void serializeReturn(Instruction instruction) {
			if(instruction instanceof ValueInstruction){
				Object value = ((ValueInstruction) instruction).value;
				if(value==null){
					outputStream.printf("<%s />", RETURN_TAG);
					return;
				} else if(value.equals(SpecialSymbols.BREAK)){
					outputStream.printf("<%s />", BREAK_TAG);
					return;
				} else if(value.equals(SpecialSymbols.CONTINUE)){
					outputStream.printf("<%s />", CONTINUE_TAG);
					return;
				}
			}
			String valueExpression = deconstructExpression(instruction);
			outputStream.printf("<%s %s=\"%s\" />", RETURN_TAG, VALUE_ATTRIBUTE, valueExpression);
		}
		
		
		/*********************** EXPRESSION LEVEL ***********************/

		/**
		 * Transforms an Instruction object into it's String equivalent.
		 * @param instruction - the Instruction to be transformed
		 * @return - String representing the Instruction
		 */
		protected String deconstructExpression(Instruction instruction) {
			StringBuilder sb = new StringBuilder();
			
			if(instruction instanceof ValueInstruction){										//a value instruction
				ValueInstruction vi = (ValueInstruction) instruction;
				if(vi.value instanceof String){						//String has ''
					sb.append("'");
					sb.append(vi.value);
					sb.append("'");
				} else {											//the others don't
					sb.append(vi.value);
				}
				
			} else if(instruction instanceof VariableWithIndexInstruction){						
				VariableWithIndexInstruction vwii = (VariableWithIndexInstruction) instruction;	//a variable with index
				sb.append(vwii.name);
				for(Instruction index : vwii.indexValues){
					sb.append("[");
					sb.append(deconstructExpression(index));
					sb.append("]");
				}
				
			} else if(instruction instanceof VariableInstruction){								//a simple variable
				sb.append(((VariableInstruction)instruction).name);
				
			} else if(instruction instanceof OneOperandInstruction){				//ONE OPERAND
				if(instruction instanceof NotInstruction){							//not instruction
					sb.append("!");
					sb.append(deconstructExpression(((NotInstruction) instruction).operand));
				}
				
			} else if(instruction instanceof TwoOperandsInstruction){				//TWO OPERANDS
				sb.append("(");
				sb.append(deconstructExpression(((TwoOperandsInstruction) instruction).leftOperand));
				
				if(instruction instanceof AssignInstruction){							//assign instruction
					sb.append("=");
				} else if(instruction instanceof AdditionInstruction){					//addition instruction
					sb.append("+");
				} else if(instruction instanceof SubtractionInstruction){				//subtraction instruction
					sb.append("-");
				} else if(instruction instanceof MultiplicationInstruction){			//multiplication instruction
					sb.append("*");
				} else if(instruction instanceof DivisionInstruction){					//division instruction
					sb.append("/");
				} else if(instruction instanceof ModuloInstruction){					//modulo instruction
					sb.append("%");
				} else if(instruction instanceof ModuloInstruction){					//modulo instruction
					sb.append("%");
				} else if(instruction instanceof LessInstruction){						//less instruction
					sb.append("&lt;");
				} else if(instruction instanceof LessOrEqualInstruction){				//less or equal instruction
					sb.append("&lt;=");
				} else if(instruction instanceof GreaterInstruction){					//greater instruction
					sb.append("&gt;");
				} else if(instruction instanceof GreaterOrEqualInstruction){			//greater or equal instruction
					sb.append("&gt;=");
				} else if(instruction instanceof EqualsInstruction){					//equals instruction
					sb.append("==");
				} else if(instruction instanceof NotEqualsInstruction){					//not equals instruction
					sb.append("!=");
				} else if(instruction instanceof AndInstruction){						//and instruction
					sb.append("&amp;&amp;");
				} else if(instruction instanceof OrInstruction){						//or instruction
					sb.append("||");
				}
				
				sb.append(deconstructExpression(((TwoOperandsInstruction) instruction).rightOperand));
				sb.append(")");
				
			} else if(instruction instanceof FunctionInstruction){					//function instructions
				if(instruction instanceof GetCurrentMessageFromInstruction){
					sb.append(GET_CURRENT_MESSAGE_FROM_FUNCTION);
				} else if(instruction instanceof GetCurrentMessageDataInstruction){
					sb.append(GET_CURRENT_MESSAGE_DATA_FUNCTION);
				} else if(instruction instanceof IsCurrentMessageNullInstruction){
					sb.append(IS_CURRENT_MESSAGE_NULL_FUNCTION);
				} else if(instruction instanceof ExecuteTransitionInstruction){
					sb.append(EXECUTE_TRANSITION_FUNCTION);
				} else if(instruction instanceof SizeInstruction){
					sb.append(SIZE_FUNCTION);
				} else if(instruction instanceof SplitInstruction){
					sb.append(SPLIT_FUNCTION);
				} else if(instruction instanceof ParseIntegerInstruction){
					sb.append(PARSE_INTEGER_FUNCTION);
				}
				
				sb.append("(");
				if(((FunctionInstruction)instruction).parameters!=null){
					for(Instruction parameter : ((FunctionInstruction)instruction).parameters){	//parameter serialization
						sb.append(deconstructExpression(parameter));
						sb.append(",");
					}
				}
				if(sb.charAt(sb.length()-1)==','){
					sb.deleteCharAt(sb.length()-1);
				}
				sb.append(")");
			}
			
			return sb.toString();
		}
	}
}
